package org.skycloud.mybatis.type;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.ibatis.type.BigDecimalTypeHandler;
import org.apache.ibatis.type.BigIntegerTypeHandler;
import org.apache.ibatis.type.BooleanTypeHandler;
import org.apache.ibatis.type.ByteArrayTypeHandler;
import org.apache.ibatis.type.ByteObjectArrayTypeHandler;
import org.apache.ibatis.type.ByteTypeHandler;
import org.apache.ibatis.type.CharacterTypeHandler;
import org.apache.ibatis.type.DateTypeHandler;
import org.apache.ibatis.type.FloatTypeHandler;
import org.apache.ibatis.type.IntegerTypeHandler;
import org.apache.ibatis.type.LongTypeHandler;
import org.apache.ibatis.type.ObjectTypeHandler;
import org.apache.ibatis.type.ShortTypeHandler;
import org.apache.ibatis.type.StringTypeHandler;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeReference;

public abstract class AbstractTypeHandler<T, JdbcType> extends TypeReference<T> implements TypeHandler<T> {

	private static final Map<Type, TypeHandler<?>> TYPE_HANDLERS = new HashMap<Type, TypeHandler<?>>();

	static {
		TYPE_HANDLERS.put(String.class, new StringTypeHandler());
		TYPE_HANDLERS.put(Boolean.class, new BooleanTypeHandler());
		TYPE_HANDLERS.put(Byte.class, new ByteTypeHandler());
		TYPE_HANDLERS.put(Character.class, new CharacterTypeHandler());
		TYPE_HANDLERS.put(Date.class, new DateTypeHandler());
		TYPE_HANDLERS.put(Float.class, new FloatTypeHandler());
		TYPE_HANDLERS.put(Integer.class, new IntegerTypeHandler());
		TYPE_HANDLERS.put(Long.class, new LongTypeHandler());
		TYPE_HANDLERS.put(Short.class, new ShortTypeHandler());
		TYPE_HANDLERS.put(BigDecimal.class, new BigDecimalTypeHandler());
		TYPE_HANDLERS.put(BigInteger.class, new BigIntegerTypeHandler());
		TYPE_HANDLERS.put(Byte[].class, new ByteObjectArrayTypeHandler());
		TYPE_HANDLERS.put(byte[].class, new ByteArrayTypeHandler());
		TYPE_HANDLERS.put(Object.class, new ObjectTypeHandler());
	}

	private final TypeHandler<JdbcType> typeHandler;

	public AbstractTypeHandler() {
		Type type = getClass().getGenericSuperclass();
		if (type instanceof ParameterizedType) {
			typeHandler = createTypeHandler(((ParameterizedType) type).getActualTypeArguments()[1]);
		} else {
			throw new IllegalArgumentException("Can't determine generic type of class [" + getClass().getName() + "].");
		}
	}

	private TypeHandler<JdbcType> createTypeHandler(Type type) {
		@SuppressWarnings("unchecked")
		TypeHandler<JdbcType> typeHandler = (TypeHandler<JdbcType>) TYPE_HANDLERS.get(type);
		if (typeHandler == null) {
			throw new IllegalArgumentException("JdbcType [" + ((Class<?>) type).getName()
					+ "] not supported, supported types are: " + TYPE_HANDLERS.keySet());
		}
		return typeHandler;
	}

	@Override
	public final void setParameter(PreparedStatement ps, int i, T parameter, org.apache.ibatis.type.JdbcType jdbcType)
			throws SQLException {
		typeHandler.setParameter(ps, i, handleParameter(parameter), jdbcType);
	}

	@Override
	public final T getResult(ResultSet rs, String columnName) throws SQLException {
		return handleResult(typeHandler.getResult(rs, columnName));
	}

	@Override
	public final T getResult(ResultSet rs, int columnIndex) throws SQLException {
		return handleResult(typeHandler.getResult(rs, columnIndex));
	}

	@Override
	public final T getResult(CallableStatement cs, int columnIndex) throws SQLException {
		return handleResult(typeHandler.getResult(cs, columnIndex));
	}

	protected abstract JdbcType handleParameter(T parameter);

	protected abstract T handleResult(JdbcType result);
}
